本文主要會談到
原生功能(Natives)其實指的就是「內建函式」(built-in function),最常用的像是 String()
、Number()
、Boolean()
、Array()
、Object()
、Function()
、RegExp()
、Date()
、Error()
、Symbol()
,其中 null 和 undefined 是沒有內建函式的。我們也可以將 Natives 當成建構子(constructor)來建立值。注意,使用建構子建立出來的值是一個包裹了基本型別值的物件包裹器(object wrapper),而這個包裹器在其原型(prototype)上定義了許多屬性和方法,因此這些資料型態就能如物件般擁有屬性和方法以供使用。
範例如下,使用 new String('...')
來建立字串值「Hello World!」,
const s = new String('Hello World!');
s // String {"Hello World!"}
s.toString() // "Hello World!"
typeof s // "object"
s instanceof String // true
Object.prototype.toString.call(s); // "[object String]"
說明
s.toString()
得到字串值。typeof s
檢視 s 的型別,結果是「物件」。s instanceof String
確認 s 為 String 的實體物件。Object.prototype.toString
取得物件的子分類,得到字串。[[Class]]
物件型別的值其內部有一個 [[Class]]
屬性來標記這個值是屬於物件的哪個子分類,雖然無法直接取用,但可透過 Object.prototype.toString
間接取得,範例如下。
Object.prototype.toString.call(123456789); // "[object Number]"
Object.prototype.toString.call('Hello World'); // "[object String]"
Object.prototype.toString.call(true); // "[object Boolean]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call([1, 2, 3]); // "[object Array]"
Object.prototype.toString.call({ name: 'Jack' }); // "[object Object]"
Object.prototype.toString.call(function sayHi() {}); // "[object Function]"
Object.prototype.toString.call(/helloworld/i); // "[object RegExp]"
Object.prototype.toString.call(new Date()); // "[object Date]"
Object.prototype.toString.call(Symbol('foo')); // "[object Symbol]"
由於 JavaScript 引擎會自動為基本型別值包裹(或稱封裝)物件包裹器,因此字面值能有屬性或方法可用,例如
const s = 'Hello World!';
s.length // 12
那麼,直接使用物件形式的物件包裹器來宣告變數,而非隱含地讓 JavaScript 引擎轉換,是不是比較好呢?答案是否定的,第一,這樣效能不佳,使用字面值可讓 JavaScript 預先編譯並快取起來!第二,沒有必要,字面值可幾乎可完全取代物件包裹器做的事情-因此,就讓 JavaScript 引擎自動為我們做這個封裝的工作吧。
const s = new String('Hello World!'); // 錯誤示範!效能差!
s.length // 12
const s_the_other = Object('Hello World!'); // 錯誤示範!效能差!
s_the_other.length // 12
const s_another = 'Hello World!'; // 正確示範!效能佳!
s_another.length // 12
由於直接使用物件形式的物件包裹器來宣告變數會造成一些誤用,像是難以做條件判斷,因此非常不建議這麼做!
使用之前...請。三。思!
...
...
如下範例,使用物件包裹器宣告一個布林變數 isValid,其值希望是 false,但實際上卻是一個物件 Boolean {true}
,導致進入判斷式時轉型為 true,印出訊息「可以繼續運作...」。
const isValid = new Boolean(false);
if (isValid) {
console.log('可以繼續運作...');
} else {
console.log('不合規則,等待處理...');
}
// 可以繼續運作...
...
...
怎麼辦?很簡單,「解封裝」就行啦!繼續看下去吧。
解封裝是指將其底層的基本型別值取出來。
承上範例,isValid 的值居然是物件 Boolean {true}
,只好使用 valueOf
來抽出底層的基型值摟,其他強制轉型的方法待後強制轉型的部份補充。
isValid.valueOf() // false
再次強調,優先使用字面值而非使用建構子建立物件。但在這個「建構子的原生功能」部份,我們還是來看一些需要關心的議題和警惕用的錯誤用法。
Array(..)
new Array(...)
和 Array(...)
同義。const a = Array(10);
a // (10) [empty × 10]
a.length // 10
const b = [undefined, undefined, undefined];
delete b[1] // true,成功刪除一個元素?
b // [undefined, empty, undefined],這裡產生一個空插槽!
RegExp(..)
在正規表達式方面,只有一種狀況會需要用到物件包裹器而非字面值,就是必須「動態地」為正規表達式建立範式(pattern),意即 new RegExp('pattern', 'flags')
的格式。
const name = 'Apple';
const pattern = new RegExp("\\b(?:" + name + ")+\\b", "ig");
const matches = 'Hi, Apple'.match(pattern);
matches // ["Apple"]
Date(..)
與 Error(..)
Date 與 Error 沒有字面值格式,只能用物件包裹器作為建構子的方式建立物件。
Error 需要注意的地方是,不管是否使用 new,陣列的物件包裹器所建立的物件是相同的,意即 new Error(...)
和 Error(...)
同義。
Symbol(..)
Symbol 同樣沒有字面值格式,若要自定義的 Symbol,就要使用建構子 Symbol(..)
且不可在前面加上 new,否則會報錯。
每個建構子都有自己的 .prototype
物件,例如:Array.prototype
、String.prototype
等,而這些 .prototype
物件擁有各自子物件的專屬行為。白話來說,就是經由建構子建立的物件與經由 JavaScript 引擎封裝的字面值,由於原型委派(prototype delegation)的緣故,都能使用定義於 .prototype
的屬性和方法。例如,無論是經由 String()
建構子或經由 JavaScript 引擎封裝的字串基本型別字面值,由於原型委派(prototype delegation)的緣故,都能使用定義於 String.prototype
的屬性和方法。又, String.prototype.XYZ
可簡寫為 String#XYZ
,例如:String#indexOf(..)
、String#charAt(..)
等,其他型別都各自有其行為。
注意,不要任意修改這些預設的原生的原型(甚至建議不要無條件地擴充原生的原型,若要擴充也應撰寫符合規格的測試程式),這在後續強制轉型的部份會看到一些例子(心酸血淚,哭 (〒︿〒))。
...
...
來人啊,還不趕快點辛曉琪的領悟?
「啊 多麼痛的領悟」
什麼?你說這歌太老沒聽過?
...
...
Array.prototype
是空陣列,Function.prototype
是空的函式,RegExp.prototype
是空的正規表達式,因此有人會拿來做為變數的初始值,雖然可能節省了重新創建新值和垃圾回收的工作而讓效能變好,但這可能會在無意間修改了這些預設的原生的原型,這是要避免的。
看完這篇文章,我們到底有什麼收穫呢?藉由本文可以理解到...
String(..)
、Number(..)
等,除了使用字面值,也可用 Natives 當成建構子來建立值。同步發表於部落格。
作者很認真在提供資訊,可是偏向理論和語法,這些在網路上能找到更詳盡完整的內容(MDN)。建議作者提供更多屬於應用層次的內容,或者丟問題然後提供解決步驟。簡單來說,這個系列多的是事實,缺的是思辨和實務應用